package cn.jeasy.core.bean.support;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.jeasy.core.util.StringUtil;

/**
 * 类扫描器抽象类
 * @author Fred
 *
 */
public abstract class AbstractScanner {

    private static final Logger logger = LoggerFactory.getLogger(AbstractScanner.class);
    private List<Class<?>> classList=new ArrayList<Class<?>>();
    protected final String packageName;

    protected AbstractScanner(String packageName) {
    	logger.debug("scanning package: {}",packageName);
        this.packageName = packageName;
       
    }

    /**
     * 扫描包下所有类
     * @return
     */
    public final List<Class<?>> getClassList() {
    	 startScanner();
        return classList;
    }
    
    private void startScanner(){
    	 try {
             // 从包名获取 URL 类型的资源
             Enumeration<URL> urls =Thread.currentThread().getContextClassLoader().getResources(packageName.replace(".", "/"));
             // 遍历 URL 资源
             while (urls.hasMoreElements()) {
                 URL url = urls.nextElement();
                 if (url != null) {
                 	logger.debug(url.toString());
                     // 获取协议名（分为 file 与 jar）
                     String protocol = url.getProtocol();
                     if (protocol.equals("file")) {
                         // 若在 class 目录中，则执行添加类操作
                         String packagePath = url.getPath().replaceAll("%20", " ");
                         scanClassByPackagePath(packagePath, packageName);
                     } else if (protocol.equals("jar")) {
                     	scanClassByJar(url);
                     }
                 }
             }
         } catch (Exception e) {
             logger.error("getClassList Exception", e);
         }
    }
    
     /**
      * 扫描jar包
      * @param url
      * @throws IOException
      */
    private void scanClassByJar(URL url ) throws IOException{
    	  // 若在 jar 包中，则解析 jar 包中的 entry
        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
        JarFile jarFile = jarURLConnection.getJarFile();
        Enumeration<JarEntry> jarEntries = jarFile.entries();
        while (jarEntries.hasMoreElements()) {
            JarEntry jarEntry = jarEntries.nextElement();
            String jarEntryName = jarEntry.getName();
            // 判断该 entry 是否为 class
            if (jarEntryName.endsWith(".class")) {
                // 获取类名
                String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
                // 执行添加类操作
                addClass(className);
            }
        }
    }
    /**
     * 扫描包路径下的class
     * @param packagePath
     * @param packageName
     */
    private void scanClassByPackagePath(String packagePath, String packageName) {
        try {        	
            // 获取包名路径下的 class 文件或目录
            File[] files = new File(packagePath).listFiles(new FileFilter() {
                @Override
                public boolean accept(File file) {
                    return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
                }
            });
            // 遍历文件或目录
            for (File file : files) {
                String fileName = file.getName();
                // 判断是否为文件或目录
                if (file.isFile()) {
                    // 获取类名
                    String className = fileName.substring(0, fileName.lastIndexOf("."));
                    if (StringUtil.isNotEmpty(packageName)) {
                        className = packageName + "." + className;
                    }
                    // 执行添加类操作
                    addClass(className);
                } else {
                    // 获取子包
                    String subPackagePath = fileName;
                    if (StringUtil.isNotEmpty(packagePath)) {
                        subPackagePath = packagePath + "/" + subPackagePath;
                    }
                    // 子包名
                    String subPackageName = fileName;
                    if (StringUtil.isNotEmpty(packageName)) {
                        subPackageName = packageName + "." + subPackageName;
                    }
                    // 递归调用
                    scanClassByPackagePath(subPackagePath, subPackageName);
                }
            }
        } catch (Exception e) {
            logger.error("scanClassNameByPackagePath Exception", e);
        }
    }

    private void addClass(String className) {
        // 加载类
        Class<?> cls = loadClass(className, false);
        // 判断是否可以添加类 
        if (checkAddClass(cls)) {
            // 添加类
        	logger.debug("addClass:{}",className);
            classList.add(cls);
        }
    }
    /**
     * 加载类
     */
    public static Class<?> loadClass(String className, boolean isInitialized) {
        Class<?> cls;
        try {
        	logger.debug("load class ["+className+"],isInitialized="+isInitialized);
            cls = Class.forName(className, isInitialized, Thread.currentThread().getContextClassLoader());
        } catch (ClassNotFoundException e) {
            logger.error("loadClass:class not found [{}]",className, e);
            throw new RuntimeException(e);
        }
        return cls;
    }
    /**
     * 验证是否允许添加类
     */
    public abstract boolean checkAddClass(Class<?> cls);
}